package edu.sjsu.cs286.teodoro.cipresso.bluetooth;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import javax.microedition.io.StreamConnection;

import edu.sjsu.cs286.teodoro.cipresso.playfield.BlueTankGameCanvas;
import edu.sjsu.cs286.teodoro.cipresso.sprites.BlueTankVehicleSprite;
import edu.sjsu.cs286.teodoro.cipresso.wizard.BlueTankGameContainer;

/**
 * Instances of this class are meant to be run on a seperate thread, for the
 * purpose of updating the game client with the opponents status. The message
 * handler is essentially an event handler which implements a two-phase commit
 * between the peer devices so that they maintain a consistent game state.
 * 
 * @author Teodoro Cipresso
 */
public class BlueTankMessageHandler implements Runnable {

	// markup delimiter tags and attributes

	private static final String TANK_DRIVE_COMMIT_REQUEST_TAG = ".0TDCR";

	private static final String TANK_DRIVE_COMMIT_TAG = ".1TDC";

	private static final String TANK_ROTATE_COMMIT_REQUEST_TAG = ".2TRCR";

	private static final String TANK_ROTATE_COMMIT_TAG = ".3TRC";

	private static final String TANK_FIRE_COMMIT_REQUEST_TAG = ".4TFCR";

	private static final String TANK_FIRE_COMMIT_TAG = ".5TFC";

	private static final String GAME_WIN_COMMIT_REQUEST_TAG = ".6GWCR";

	private static final String GAME_WIN_COMMIT_TAG = ".7GWC";

	private static final String TANK_SELC_NOTIFY_TAG = ".8TSN";

	private static final String BOOL_VALUE_TAG = ".B=";

	private static final String DLTA_VALUE_TAG = ".D=";

	private static final String MLLS_VALUE_TAG = ".M=";

	private static final String NAME_VALUE_TAG = ".N=";

	private static final String ECHO_EVNT_TAG = ".ECHO";

	public static final int COMMIT_DELAY = 128;

	public boolean running;

	private DataOutputStream outputStream;

	private DataInputStream inputStream;

	private StreamConnection streamConn;

	private BlueTankGameContainer container = null;

	private BlueTankGameCanvas canvas = null;

	private BlueTankVehicleSprite peerTank = null;

	private BlueTankVehicleSprite selfTank = null;

	/**
	 * Default constructor. Gets input and output data streams.
	 */
	public BlueTankMessageHandler(BlueTankGameContainer parent) {
		super();
		container = parent;
		canvas = container.getBtCanvas();
		//
		try {
			streamConn = container.getBtManager().getStreamConn();
			inputStream = streamConn.openDataInputStream();
			outputStream = streamConn.openDataOutputStream();
		} catch (IOException e) {
			e.printStackTrace();
		}
	} // BlueTankMessageHandler()

	/**
	 * markup listen loop. Read will block until a markup is received. Loop will
	 * terminate if socket closed previously.
	 */
	public void run() {
		// initially
		running = true;
		//
		try {
			//
			while (running) {
				//
				String markup = inputStream.readUTF();
				//
				if (markup != null && markup.length() > 0) {
					if (markup.startsWith(TANK_DRIVE_COMMIT_REQUEST_TAG)) {
						handleTankDriveCommitRequest(markup);
					} else if (markup.startsWith(TANK_DRIVE_COMMIT_TAG)) {
						handleTankDriveCommit(markup);
					} else if (markup
							.startsWith(TANK_ROTATE_COMMIT_REQUEST_TAG)) {
						handleTankRotateCommitRequest(markup);
					} else if (markup.startsWith(TANK_ROTATE_COMMIT_TAG)) {
						handleTankRotateCommit(markup);
					} else if (markup.startsWith(TANK_FIRE_COMMIT_REQUEST_TAG)) {
						handleTankFireCommitRequest(markup);
					} else if (markup.startsWith(TANK_FIRE_COMMIT_TAG)) {
						handleTankFireCommit(markup);
					} else if (markup.startsWith(TANK_SELC_NOTIFY_TAG)) {
						handleTankSelectEvent(markup);
					} else if (markup.startsWith(GAME_WIN_COMMIT_REQUEST_TAG)) {
						handleGameWinCommitRequest(markup);
					} else if (markup.startsWith(GAME_WIN_COMMIT_TAG)) {
						handleGameWinCommit(markup);
					} else if (markup.startsWith(ECHO_EVNT_TAG)) {
						writeOutputStream(markup);
					} // check markup type
				} // markup not null
			} // while playing
		} catch (Exception e) {
			e.printStackTrace();
		}
	} // run()

	/**
	 * Notify peer that our tank drove forward or backward.
	 */
	public void sendTankDriveCommitRequest(int delta, long m) {
		String markup = new String(TANK_DRIVE_COMMIT_REQUEST_TAG);
		markup += DLTA_VALUE_TAG + delta;
		markup += MLLS_VALUE_TAG + m;
		markup += TANK_DRIVE_COMMIT_REQUEST_TAG;
		writeOutputStream(markup);
	} // sendTankDriveEvent()

	/**
	 * Notify peer that our tank drove forward or backward.
	 */
	public boolean sendTankDriveCommit(int delta, long m) {
		String markup = new String(TANK_DRIVE_COMMIT_TAG);
		markup += DLTA_VALUE_TAG + delta;
		markup += MLLS_VALUE_TAG + m;
		markup += TANK_DRIVE_COMMIT_TAG;
		return writeOutputStream(markup);
	} // sendTankDriveCommit()

	/**
	 * Notify peer that our tank rotated forward or backward.
	 */
	public void sendTankRotateCommitRequest(int delta, long m) {
		String markup = new String(TANK_ROTATE_COMMIT_REQUEST_TAG);
		markup += DLTA_VALUE_TAG + delta;
		markup += MLLS_VALUE_TAG + m;
		markup += TANK_ROTATE_COMMIT_REQUEST_TAG;
		writeOutputStream(markup);
	} // sendTankRotateCommitRequest()

	/**
	 * Notify peer that our tank rotated forward or backward.
	 */
	public boolean sendTankRotateCommit(int delta, long m) {
		String markup = new String(TANK_ROTATE_COMMIT_TAG);
		markup += DLTA_VALUE_TAG + delta;
		markup += MLLS_VALUE_TAG + m;
		markup += TANK_ROTATE_COMMIT_TAG;
		return writeOutputStream(markup);
	} // sendTankRotateCommit()

	/**
	 * Notify peer that we want to fire the cannon. Synchronization is
	 * necessary.
	 * 
	 * @param m,
	 *            current system time
	 */
	public void sendTankFireCommitRequest(long m) {
		String markup = new String(TANK_FIRE_COMMIT_REQUEST_TAG);
		markup += MLLS_VALUE_TAG + m;
		markup += TANK_FIRE_COMMIT_REQUEST_TAG;
		writeOutputStream(markup);
	} // sendTankFireCommitRequest()

	/**
	 * Respond to peer with a time in the future when the cannon can be fired.
	 * This is to synchronize the firing of the cannon on both clients.
	 * 
	 * @param m,
	 *            time in the future based on peers system time
	 */
	public boolean sendTankFireCommit(long m) {
		String markup = new String(TANK_FIRE_COMMIT_TAG);
		markup += MLLS_VALUE_TAG + m;
		markup += TANK_FIRE_COMMIT_TAG;
		return writeOutputStream(markup);
	} // sendTankFireACKEvent()

	/**
	 * Notify peer of the tank we selected
	 * 
	 * @param tankName,
	 *            tank name constant from IBlueTankSprite
	 */
	public void sendTankSelectNotify(String tankName) {
		String markup = new String(TANK_SELC_NOTIFY_TAG);
		markup += NAME_VALUE_TAG + tankName;
		markup += TANK_SELC_NOTIFY_TAG;
		writeOutputStream(markup);
	} // sendTankSelectEvent()

	/**
	 * Ask peer to agree that we've won the game
	 */
	public void sendGameWinCommitRequest() {
		String markup = new String(GAME_WIN_COMMIT_REQUEST_TAG);
		markup += GAME_WIN_COMMIT_REQUEST_TAG;
		writeOutputStream(markup);
	} // sendGameWinCommitRequest()

	/**
	 * Tell peer we either agree or disagree with won. If agreement cannot be
	 * reached, the game is assumed to be a tie, since their is no third party
	 * (server) to resolve the disagreement
	 */
	public boolean sendGameWinCommit(boolean agree) {
		String markup = new String(GAME_WIN_COMMIT_TAG);
		markup += BOOL_VALUE_TAG
				+ ((agree) ? Integer.toString(1) : Integer.toString(0));
		markup += GAME_WIN_COMMIT_TAG;
		return writeOutputStream(markup);
	} // sendGameWinCommit()

	/**
	 * Update peer sprite that it has driven forward or backwards
	 * 
	 * @markup, complete markup received from peer (needs to be parsed)
	 */
	public void handleTankDriveCommitRequest(String markup) {
		// set and check sprites
		if (!tankSpritesAreSet())
			return;
		// Delta
		int sD = markup.indexOf(DLTA_VALUE_TAG) + DLTA_VALUE_TAG.length();
		int eD = markup.indexOf(".", sD);
		int vD = Integer.parseInt(markup.substring(sD, eD));
		// Time
		int sT = markup.indexOf(MLLS_VALUE_TAG) + MLLS_VALUE_TAG.length();
		int eT = markup.indexOf(TANK_DRIVE_COMMIT_REQUEST_TAG, sT);
		long vT = Long.parseLong(markup.substring(sT, eT));
		// Setup commit times
		long tP = System.currentTimeMillis() + COMMIT_DELAY;
		long tS = vT + COMMIT_DELAY;
		// Complete transaction
		if (sendTankDriveCommit(vD, tS)) {
			peerTank.doDrive(vD, tP);
		}
	} // handleTankDriveCommitRequest()

	/**
	 * Update peer sprite that it has driven forward or backwards
	 * 
	 * @markup, complete markup received from peer (needs to be parsed)
	 */
	public void handleTankDriveCommit(String markup) {
		// set and check sprites
		if (!tankSpritesAreSet())
			return;
		// Delta
		int sD = markup.indexOf(DLTA_VALUE_TAG) + DLTA_VALUE_TAG.length();
		int eD = markup.indexOf(".", sD);
		int vD = Integer.parseInt(markup.substring(sD, eD));
		// Time
		int sT = markup.indexOf(MLLS_VALUE_TAG) + MLLS_VALUE_TAG.length();
		int eT = markup.indexOf(TANK_DRIVE_COMMIT_TAG, sT);
		long vT = Long.parseLong(markup.substring(sT, eT));
		// Complete Transaction
		selfTank.doDrive(vD, vT);
	} // handleTankDriveEvent()

	/**
	 * Update peer sprite that it has rotated on its axis to one of the 16
	 * vectors
	 * 
	 * @markup, complete markup received from peer (needs to be parsed)
	 */
	public void handleTankRotateCommitRequest(String markup) {
		// set and check sprites
		if (!tankSpritesAreSet())
			return;
		// Delta
		int sD = markup.indexOf(DLTA_VALUE_TAG) + DLTA_VALUE_TAG.length();
		int eD = markup.indexOf(".", sD);
		int vD = Integer.parseInt(markup.substring(sD, eD));
		// Time
		int sT = markup.indexOf(MLLS_VALUE_TAG) + MLLS_VALUE_TAG.length();
		int eT = markup.indexOf(TANK_ROTATE_COMMIT_REQUEST_TAG, sT);
		long vT = Long.parseLong(markup.substring(sT, eT));
		// Setup commit times
		long tP = System.currentTimeMillis() + COMMIT_DELAY;
		long tS = vT + COMMIT_DELAY;
		// Complete transaction
		if (sendTankRotateCommit(vD, tS)) {
			peerTank.doRotate(vD, tP);
		}
	} // handleTankRotateCommitRequest()

	/**
	 * Update peer sprite that it has rotated on its axis to one of the 16
	 * vectors
	 * 
	 * @markup, complete markup received from peer (needs to be parsed)
	 */
	public void handleTankRotateCommit(String markup) {
		// set and check sprites
		if (!tankSpritesAreSet())
			return;
		// Delta
		int sX = markup.indexOf(DLTA_VALUE_TAG) + DLTA_VALUE_TAG.length();
		int eX = markup.indexOf(".", sX);
		int vX = Integer.parseInt(markup.substring(sX, eX));
		// Get Commit Time
		int sT = markup.indexOf(MLLS_VALUE_TAG) + MLLS_VALUE_TAG.length();
		int eT = markup.indexOf(TANK_ROTATE_COMMIT_TAG, sT);
		long vT = Long.parseLong(markup.substring(sT, eT));
		// Complete Transaction
		selfTank.doRotate(vX, vT);
	} // handleTankRotateCommit()

	/**
	 * This method is triggered by peer wanting to synchronize for firing the
	 * cannon. We acknowledge the request by replying with a time in the future
	 * (based on relative system clocks) that the cannon can be fired.
	 * 
	 * @markup, complete markup received from peer (needs to be parsed)
	 */
	public void handleTankFireCommitRequest(String markup) {
		// set and check sprites
		if (!tankSpritesAreSet())
			return;
		//
		int sM = markup.indexOf(MLLS_VALUE_TAG) + MLLS_VALUE_TAG.length();
		int eM = markup.indexOf(TANK_FIRE_COMMIT_REQUEST_TAG, sM);
		long vM = Long.parseLong(markup.substring(sM, eM));
		// Setup commit times
		long tP = System.currentTimeMillis() + COMMIT_DELAY;
		long tS = vM + COMMIT_DELAY;
		// Complete transaction
		if (sendTankFireCommit(tS)) {
			peerTank.doFireCannon(tP);
		}
	} // handleTankFireCommitRequest()

	/**
	 * This method is called when received the OK and a time in the future from
	 * the peer to complete the firing request we started earlier. *
	 * 
	 * @markup, complete markup received from peer (needs to be parsed)
	 */
	public void handleTankFireCommit(String markup) {
		// set and check sprites
		if (!tankSpritesAreSet())
			return;
		//
		int sM = markup.indexOf(MLLS_VALUE_TAG) + MLLS_VALUE_TAG.length();
		int eM = markup.indexOf(TANK_FIRE_COMMIT_TAG, sM);
		long vM = Long.parseLong(markup.substring(sM, eM));
		//
		selfTank.doFireCannon(vM);
	} // handleTankFireCommit()

	/**
	 * This method is called when the peer notifies us of the tank they
	 * selected, so we can set the appropriate Sprite images on our map.
	 * 
	 * @markup, complete markup received from peer (needs to be parsed)
	 */
	public void handleTankSelectEvent(String markup) {
		// Select ownerTank
		int sS = markup.indexOf(NAME_VALUE_TAG) + NAME_VALUE_TAG.length();
		int eS = markup.indexOf(TANK_SELC_NOTIFY_TAG, sS);
		String vS = markup.substring(sS, eS);
		//
		container.setPeerTankByName(vS);
	} // handleTankSelectEvent()

	/**
	 * This method is called when the peer notifies us that a winner has been
	 * detected. This client has to see if it agrees otherwise,
	 */
	public void handleGameWinCommitRequest(String markup) {
		// check for agreement
		if (canvas.isPeerWon() && !canvas.isSelfWon()) {
			// agree
			if (sendGameWinCommit(true)) {
				canvas.setGameLost(true);
			}
		} else {
			// disagree
			if (sendGameWinCommit(false)) {
				canvas.setGameDraw(true);
			}
		}
		//
	} // handleGameWinCommitRequest()

	/**
	 * This method is called when the peer notifies us whether they agreed with
	 * whom we though won the game. Disagreement always equals a tie.
	 */
	public void handleGameWinCommit(String markup) {
		// Select ownerTank
		int sS = markup.indexOf(BOOL_VALUE_TAG) + BOOL_VALUE_TAG.length();
		int eS = markup.indexOf(GAME_WIN_COMMIT_TAG, sS);
		int vS = Integer.parseInt(markup.substring(sS, eS));
		boolean agree = (vS == 1) ? Boolean.TRUE.booleanValue() : Boolean.FALSE
				.booleanValue();
		// if there is agreement, then there exists one loser and one winner
		if (agree) {
			canvas.setGameWon(true);
		} else {
			canvas.setGameDraw(true);
		}
		//
	} // handleGameWinACKEvent()

	/**
	 * This is a utility method to ensure that the tank sprites are non-null
	 * before attempting to their change their state based on recieved events.
	 */
	public boolean tankSpritesAreSet() {
		//
		if (peerTank == null) {
			peerTank = container.getBtCanvas().getPeerTank();
		}
		if (selfTank == null) {
			selfTank = container.getBtCanvas().getSelfTank();
		}
		//
		return (peerTank != null && selfTank != null);
	} // tankSpritesAreSet()

	/**
	 * This method is called by all the SEND events to write UTF-8 length
	 * prefixed messages to the peer socket connection. The assumption here is
	 * that a write error is fatal and results in a lost markup (though
	 * bluetooth is not like UDP, so if something can't be sent, the socket is
	 * likely to be closed on us).
	 * 
	 * @markup, complete markup to send
	 */
	public boolean writeOutputStream(String markup) {
		//
		if (outputStream == null || markup == null)
			return false;
		//
		try {
			// 
			outputStream.writeUTF(markup.toString());
			outputStream.flush();
			//			
		} catch (Exception e) {
			return false;
		}
		//
		return true;
	} // writeOutputStream()

	/**
	 * Close input and output streams gotten from the socket connection with
	 * peer. There really is nothing we can do if this operation fails, it's
	 * mostly to be a good socket citizen just in case the OS doesn't do this
	 * automatically.
	 */
	public void closeInputOutputStreams() {
		//
		running = false;
		//
		try {
			if (inputStream != null) {
				inputStream.close();
			}
			if (outputStream != null) {
				outputStream.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	} // closeInputOutputStreams()

	/**
	 * This method sends and immediately receives some data on the peer socket
	 * connection.
	 */
	public boolean peformDataRoundtripTest() {
		boolean result = false;
		//
		try {
			// echo message
			String markup = new String(ECHO_EVNT_TAG);
			//
			writeOutputStream(markup);
			//
			if (inputStream.readUTF().equals(markup)) {
				result = true;
			}
			//
		} catch (IOException e) {
			result = false;
			e.printStackTrace();
		}
		//
		return result;
	} // peformDataRoundtripTest()

} // BlueTankMessageHandler()

